//
//  DataSource.m
//  YuchBox
//
//  Created by Ivan on 11-10-7.
//  Copyright 2011年 __MyCompanyName__. All rights reserved.
//

#import "DataSource.h"
#import "SingletonClassGenerator.h"
#import "YuchBoxAppDelegate.h"


@implementation DataSource
SYNTHESIZE_SINGLETON_FOR_CLASS(DataSource);

@synthesize emails          = _emails;
@synthesize configuration   = _configuration;
+ (void)checkRunTimeEnvironment
{
    [IvanSqliteDB createDBEnvironment];
    [Attachment creatAttachEnvironment];
}

- (id)init
{
    self = [super init];
    if (self) {
        // Initialization code here.
        _emails              = [[NSMutableArray alloc] initWithCapacity:INIT_EMAILS_ARRAY_SIZE];
        _recvMailHashCodeMap = [[NSMutableDictionary alloc] initWithCapacity:INIT_HASHCODE_MAP_SIZE];
        _attachments         = [[NSMutableDictionary alloc] initWithCapacity:INIT_ATTACHMENT_MAP_SIZE];
        
        
        [self initConfiguration];
        
        
        // when first startup
        // 
    }
    
    return self;
}

- (id)initSingleton
{
    self = [self init];
    _currentDelegate = [YuchBoxAppDelegate shareAppDelegate];
    return self;
}

- (void)dealloc
{
    [_emails release];
    [_recvMailHashCodeMap release];
    [_configuration release];
    
    [super dealloc];
}

- (void)initConfiguration
{
    // TODO should read the default from db or login/setting view
    _configuration = [[Configuration alloc] init];
    
    
    _configuration.loginType        = CONFIGURAION_LOGIN_TYPE_SELFBUILD;
    
    if (CONFIGURAION_LOGIN_TYPE_OFFICIAL == _configuration.loginType) {
        _configuration.hostname     = [NSString stringWithFormat:@"192.168.10.10"];
        _configuration.port         = 9716;
        _configuration.userPassword = [NSString stringWithFormat:@"111111"]; 
    
    } else if(CONFIGURAION_LOGIN_TYPE_SELFBUILD == _configuration.loginType) {
        _configuration.hostname     = [NSString stringWithFormat:@"192.168.10.10"];
        _configuration.port         = 9716;
        _configuration.userPassword = [NSString stringWithFormat:@"111111"]; 
    
    } else {
        
    }
    
    _configuration.currentVersion       = [NSString stringWithFormat:@""];
    _configuration.passwordKey          = [NSString stringWithFormat:@""];
    
    _configuration.enableSSL            = FALSE;
    _configuration.enableWifi           = TRUE;
    _configuration.enableMDS            = FALSE;
    
    _configuration.isAutoRun            = TRUE;
    _configuration.isDiscardOrgText     = TRUE;
    _configuration.isDeleteRemoteMail   = FALSE;
    
    _configuration.enableWeiboModule    = FALSE;
    _configuration.enableIMModule       = FALSE;
    
    _configuration.osVersion            = [NSString stringWithFormat:@""];
    
}

- (Boolean)connect
{
    if (!_configuration.isAutoRun || 0 == _configuration.hostname.length 
        || 0 == _configuration.port || 0 == _configuration.userPassword.length) {
        
        TTLOG_ERROR(@"Configuration is not availiable!!");
        return NO;
    }
    
    // connect to the server
    if (![IVAN_NET_STREAM connectWithServer:_configuration.hostname port:_configuration.port delegate:self]) {
        TTLOG_ERROR(@"Login Failed");
        return NO;
    }
    return YES;
}

- (Boolean)login
{
    [self sendLogin:_configuration];
    return YES;
}

- (void)connectAndLogin
{
    if ([self connect]) {
        [self login];
    }
}

- (void)logout
{
    [IVAN_NET_STREAM disconnect];
}

#pragma mark - IvanNetStreamDelegate
/* Deprecated */
- (void)getData:(NSData *)receivedData flag:(int)flag
{
    // ~TODO add lock
    // ~TODO release lock
}

/* when message coming IvanNetStream callback this method int the stream RunLoop thread*/ 
- (void)readInStream:(NSInputStream *)inStream
{
    // -TODO prase the coming stream, unzip here
    NSInteger mixLen  = [StreamPeer readInt32WithInStream:inStream];
    if (mixLen <= 0) {
        TTLOGF_ERROR(@"Receive the packet head length error! <mixLen:%d>", mixLen);
        return;
    }
    
    
    NSInteger gzipLen = mixLen & 0x0000ffff;
    NSInteger orgLen  = mixLen >> 16;
    TTLOGF_DEBUG(@"Receive the packet: raw data length:%d, zip data length:%d", orgLen, gzipLen);
    
    if (gzipLen < 0 || gzipLen > 65535 || orgLen < 0 || orgLen > 65535) {
//        UInt8 *tmp = (UInt8*)malloc(192);
//        [StreamPeer readBytesWithInStream:inStream ptr:tmp maxLength:192];
//        TTDUMP(tmp, 192);
        TTLOG_ERROR(@"Receive the packet head length error!");
        return;
    }
    
    NSMutableData *orgData;
    
    if (0 == gzipLen) {
        // with no gzip
        orgData = [StreamPeer readNSDataInStream:inStream maxLength:orgLen];
    } else {
        // gzip
        NSMutableData *zipData = [StreamPeer readNSDataInStream:inStream maxLength:gzipLen];
        orgData = [NSMutableData dataWithLength:orgLen];
        uLong outLen = 0;
        
        if (![Gzip gzipinflate2WithDestData:orgData offset:0 outDestLen:&outLen source:(UInt8 *)[zipData mutableBytes] sourceLen:gzipLen]) {
            TTLOG_ERROR(@"Inflate data error!");
            TTDUMP((UInt8*)[zipData bytes], 192);
            return;
        }

//        if (orgLen == 610) {
//            TTDUMP((UInt8*)[orgData bytes], 610);
//        }
        if (outLen != orgLen) {
            TTLOG_ERROR(@"OutLen != orgLen, something error!");
            return;
        }
    }
  
    NSInputStream *orgInStream = [NSInputStream inputStreamWithData:orgData];
    [orgInStream open];
    
    NSInteger readyToRead = orgLen;
    while ([orgInStream hasBytesAvailable]) {
        if (readyToRead > 0) {
            NSInteger readLength = [self processMessage:orgInStream];
            readyToRead -= readLength;
        } else {
            break;
        }
    }
    [orgInStream close];
    orgInStream = nil;

    // TODO notify the view, there has the new email arrival    
}

- (NSData *)preProcessData:(UInt8 *)rawData length:(NSInteger)length outLength:(NSInteger *)outLength;
{
    // GZIP the data here
//    TTDUMP(rawData, length);
    
    NSInteger *msgHeader = (NSInteger*)rawData;
    *msgHeader = length - MSG_HEADER_SIZE;
//    TTDUMP(rawData, length);
    
#define GZIP_BUFFER_RATIO   1.2f
    NSInteger offset           = GZIP_HEADER_SIZE;
    NSInteger gzipBufLen       = (int)(length * GZIP_BUFFER_RATIO) + offset;
    NSMutableData *gzipBufData = [[NSMutableData alloc]initWithLength:gzipBufLen];

    
    uLong outGzipBufLen;
    
    if (![Gzip gzipDeflate2WithDestData:gzipBufData offset:offset outDestLen:&outGzipBufLen source:rawData sourceLen:length]) {
        TTLOG_ERROR(@"Gzip failed");
    }
    
    UInt8 *gzipBuf             = (UInt8 *)[gzipBufData mutableBytes];
    NSInteger outGzipBufLenTotal = outGzipBufLen + offset;
    NSInteger *gzipHeader        = (NSInteger*)gzipBuf;
    
    if (outGzipBufLenTotal < length) {
        // send the gzip data

        // to packet the data. Modifing the header(4bytes), the high address of 2 bytes 
        // save the raw data length, and low address 2 bytes save the deflated length. 
        // WARNING the max flush data length is 64KB
        *outLength  = outGzipBufLenTotal;
        *gzipHeader = ((length << 16) & 0xffff0000) | outGzipBufLen;
    } else {
        *outLength  = length + offset;
        // send the raw data with new header
        *gzipHeader = (length << 16) & 0xffff0000;
        
        memcpy(gzipBuf + offset, rawData, length);
    }

    return [gzipBufData autorelease];
    
    // default return
    //return [NSData dataWithBytesNoCopy:rawData length:length]
}

- (void)callBackHandle:(IvanNetStreamEvent)eventCode info:(id)info
{
    switch (eventCode) {
        case IvanNSEventReady:
            [_currentDelegate callBackHandle:DSEventSBReady info:info];
            break;
        case IvanNSEventErrorServerRefuse:
            // server is ping OK, yuchserver may not start.
            [_currentDelegate callBackHandle:DSEventErrorFromNet info:info];
            break;
        case IvanNSEventErrorOperationCouldNotBeCompleted:
            // network is not in the service
            [_currentDelegate callBackHandle:DSEventNetErrorOperationCannotBeCompleted info:info];
            break;
        default:
            [_currentDelegate callBackHandle:DSEventNetErrorUnknown info:info];
            break;
    }
}

#pragma mark - message processor
- (NSInteger)processMessage:(NSInputStream *)inStream
{
    // get the message length
    NSInteger msgLen  = [StreamPeer readInt32WithInStream:inStream];
    // get the message type
    NSInteger msgType = [StreamPeer readInt8WithInstream:inStream];
    
    TTLOGF_DEBUG(@"Recieve message, type: %d, length:%d", msgType, msgLen);
    switch (msgType) {
        case MSG_TYPE_MSGMAIL:
             // -TODO save the email to the datasource array.
            if (msgLen == 0) break;
            [self processRecvMail:inStream];
            break;
        case MSG_TYPE_SENDMAIL:
            // send mail successful
            [self processSentMail:inStream];
            [StreamPeer readNSDataInStream:inStream maxLength:msgLen - sizeof(UInt8)];
            break;
        case MSG_TYPE_MAILATTACH:
            [self processMailAttach:inStream];
            break;
            // Maybe save weibo or talk message later
        case MSG_TYPE_LASTVERSION:
            [StreamPeer readNSDataInStream:inStream maxLength:msgLen - sizeof(UInt8)];
            break;
        case MSG_TYPE_FILEATTCH:
            [StreamPeer readNSDataInStream:inStream maxLength:msgLen - sizeof(UInt8)];
            break;
        case MSG_TYPE_DEVICEINFO:
            // login successful
            [StreamPeer readNSDataInStream:inStream maxLength:msgLen - sizeof(UInt8)];
            
            // notify the view
            [_currentDelegate callBackHandle:DSEventSBLoginSuccessful info:nil];
            break;
        default: {
//            UInt8 * x = malloc(256);
//            memcpy(x, (UInt8*)&msgLen, 4);
//            memcpy(x+4, (UInt8*)&msgType, 1);
//            
//            [StreamPeer readBytesWithInStream:inStream ptr:x maxLength:251];
//            
//            TTDUMP(x, 256);
//            free(x);
//            return 0;
            // skip to the next message
            [StreamPeer readNSDataInStream:inStream maxLength:msgLen - sizeof(UInt8)];
            break;
        }
    }
    
    return msgLen + MSG_HEADER_SIZE;
}

/* process the email info */
- (void)processRecvMail:(NSInputStream *)inStream
{
    Email *email = [[Email alloc] initWithInputStream:inStream];
    email.status = [email.attachmentArray count] > 0 ? EMAIL_STATUS_RECV_ATTACH_UNREAD : EMAIL_STATUS_RECV_UNREAD;

    // identity the email with hash code
    NSInteger hashcode = [email getHashCode];
    
    // check if the email has received reduplicatively
    Boolean hasBeenReceived = [[_recvMailHashCodeMap objectForKey:[NSNumber numberWithInteger:hashcode]] boolValue];

    if (hasBeenReceived) {
        // send confirm message
        [self sendConfirmMsgWithHashCode:hashcode];
        
        // -TODO log for the error "has been received"
        TTLOGF_INFO(@"Email <hash:%d> has been received", hashcode);
        [email release];
        return;
    }
    
    // if the map size exceed the specific size, then remove the first object
    if ([_recvMailHashCodeMap count] > INIT_HASHCODE_MAP_SIZE * 2) {
        
        // WARNING maybe remove the lastest mail's hashcode, which hashcode is the smallest
        // program may rarely execute code here
        //
        for (id key in _recvMailHashCodeMap) {
            [_recvMailHashCodeMap removeObjectForKey:key];
            break;
        }
    }
    
    [_recvMailHashCodeMap setObject:[NSNumber numberWithBool:YES] forKey:[NSNumber numberWithInteger:hashcode]];
    
    // ~OVERDATE save the mail in memery
    // [_emails addObject:email];
    
    // save to database
    [email save:IVAN_DB];
    
    TTLOGF_INFO(@"Email(%lld) arrival, <hashcode: %u>",email.uid, hashcode);
    
    // !TODO send a confirm to server
    [self sendConfirmMsgWithHashCode:hashcode];

    // !TODO set the attachment into the datasource / database
    for (Attachment *attachment in email.attachmentArray) {
        NSNumber *attachmentKey = [NSNumber numberWithInteger:[attachment getSimpleHashCode]];
        [_attachments setObject:(id)attachment forKey:attachmentKey];

        [self sendFetchAttachment:attachment];
        attachment.uEmailId = email.uid;
        [attachment save:IVAN_DB];

        TTLOGF_INFO(@"Email(%llu) with attachment(%llu). <mailindex:%d, attachindex:%d, simplehash:%u>", email.uid, attachment.uid, attachment.mailIndex, attachment.attachmentIndex, attachment.simpleHashCode);
    }
    
    // notify the view
    [_currentDelegate callBackHandle:DSEventSBRecvMessage info:[NSNumber numberWithLongLong:email.uid]];

    [email release];
}

- (void)processSentMail:(NSInputStream *)inStream
{

}

- (void)processMailAttach:(NSInputStream *)inStream
{
    //          +---------------------------------------------------------------+
    // instream |mail index |attach index |start index |size   | attachment data...|
    //          +---------------------------------------------------------------+
    //
    NSInteger mailIndex     = [StreamPeer readInt32WithInStream:inStream];
    NSInteger attachIndex   = [StreamPeer readInt32WithInStream:inStream];

    // find the attachment
    Attachment *attachment;
    NSNumber *attachmentKey = [NSNumber numberWithInteger:[Attachment generateHashCodeWithMailIndex:mailIndex attachmentIndex:attachIndex]];
    attachment = (Attachment *)[_attachments objectForKey:attachmentKey];

    if (attachment == nil) {
        TTLOGF_ERROR(@"Can not find the attachments <mailIndex:%d + attachIndex:%d = %u", mailIndex, attachIndex, [attachmentKey intValue]);
        return;
    }
    
    [attachment readAttachInStream:inStream];
    
}

- (void)fetchAttachmentWithMailIndex:(NSInteger)mailIndex attachmentIndex:(NSInteger)attachmentIndex
{
    NSNumber *attachmentKey = [NSNumber numberWithInteger:[Attachment generateHashCodeWithMailIndex:mailIndex attachmentIndex:attachmentIndex]];
    Attachment *attachment = (Attachment *)[_attachments objectForKey:attachmentKey];
    if (attachment == nil) {
        TTLOGF_ERROR(@"Can not find the attachments <mailIndex:%d + attachIndex:%d = %u", mailIndex, attachmentIndex, [attachmentKey intValue]);
        return;
    }
    
    if([attachment hasLocalAttachmentAndLoad]) {
//        TTDUMP(attachment.contentBuffer, attachment.attachmentSize);
    } else {
        [self sendFetchAttachment:attachment];
    }
}



- (void)sendLogin:(Configuration *)configuration
{
    NSOutputStream *outStream = [IVAN_NET_STREAM getWriteFlushOutStream];
    // set the packet head size
    
    // !TODO send authorization message to server
    [StreamPeer writeInt8WithOutStream:outStream int8Value:MSG_TYPE_CONFIRM];
    [StreamPeer writeStringWithOutStream:outStream stringValue:configuration.userPassword];
    [StreamPeer writeInt32WithOutStream:outStream intValue:CLIENT_VERSION];
    [StreamPeer writeInt8WithOutStream:outStream int8Value:CLIENT_LANGUAGE_ZH_CN];
    [StreamPeer writeStringWithOutStream:outStream stringValue:configuration.currentVersion];
    [StreamPeer writeStringWithOutStream:outStream stringValue:configuration.passwordKey];
    
    // WARNING We havn't Weibo yet
    [StreamPeer writeInt8WithOutStream:outStream int8Value:configuration.enableWeiboModule];
    
    // WARNING deprecated field (only for BB). We don't use here
    [StreamPeer writeStringWithOutStream:outStream stringValue:configuration.osVersion];
    
    // WARNING weibo image size: 2bytes(high) for width and 2bytes(low) for heigh
    // 480(01E0) * 320(0140)
    [StreamPeer writeInt32WithOutStream:outStream intValue:0x01E00140];
    
    // WARNING We havn't IM yet
    [StreamPeer writeInt8WithOutStream:outStream int8Value:configuration.enableIMModule];

    [IVAN_NET_STREAM writeFlush:outStream forMode:MODE_SYNC];
    
    TTLOG_INFO(@"Send login message to the server...");
}

- (void)sendMail:(Email *)email
{
    // send attachment
    if (0 != [email.attachmentArray count]) {
        [self sendEmailAttachmentIntro:email];
    }

    NSOutputStream *outStream = [IVAN_NET_STREAM getWriteFlushOutStream];
    // TODO inited the mail
    [StreamPeer writeInt8WithOutStream:outStream int8Value:MSG_TYPE_MSGMAIL];
    [email outputMail:outStream];

    [IVAN_NET_STREAM writeFlush:outStream forMode:MODE_ASYNC];
    
    TTLOGF_INFO(@"Send email <hashcode:%u>", [email getHashCode]);
    
}

- (void)sendConfirmMsgWithHashCode:(NSInteger)hashcode
{
    NSOutputStream *outStream = [IVAN_NET_STREAM getWriteFlushOutStream];
    [StreamPeer writeInt8WithOutStream:outStream int8Value:MSG_TYPE_MAILCONFIRM];
    [StreamPeer writeInt32WithOutStream:outStream intValue:hashcode];
    
    // TODO confirm message should be buffer together
    [IVAN_NET_STREAM writeFlush:outStream forMode:MODE_ASYNC];
    TTLOGF_INFO(@"Send confirm message for mail <hashcode:%d>", hashcode);
}

- (void)sendFetchAttachment:(Attachment *)attachment
{
    NSOutputStream *outStream = [IVAN_NET_STREAM getWriteFlushOutStream];
    
    [StreamPeer writeInt8WithOutStream:outStream int8Value:MSG_TYPE_FETCHATTACH];
    [StreamPeer writeInt32WithOutStream:outStream intValue:attachment.mailIndex];
    [StreamPeer writeInt32WithOutStream:outStream intValue:attachment.attachmentIndex];
    
    [IVAN_NET_STREAM writeFlush:outStream forMode:MODE_ASYNC];
}

- (void)sendEmailAttachmentIntro:(Email *)email
{
    if (nil == email) return;
    NSOutputStream *outStream = [IVAN_NET_STREAM getWriteFlushOutStream];
    
    [StreamPeer writeInt8WithOutStream:outStream int8Value:MSG_TYPE_FILEATTCH];
    [StreamPeer writeInt32WithOutStream:outStream intValue:[email getHashCode]];
    [StreamPeer writeInt32WithOutStream:outStream intValue:[email.attachmentArray count]];
    
    for (Attachment *attachment in email.attachmentArray) {
        [StreamPeer writeInt32WithOutStream:outStream intValue:attachment.attachmentSize];
        //TTLOGF_INFO(@"%d", attachment.attachmentSize);
    }
    
    [IVAN_NET_STREAM writeFlush:outStream forMode:MODE_ASYNC];
    
    
    for (Attachment *attachment in email.attachmentArray) {
        // open file
        NSInputStream *fileInStream = [NSInputStream inputStreamWithFileAtPath:attachment.attachmentFileName];
        if (nil == fileInStream) {
            TTLOGF_ERROR(@"open file failed <%@>", attachment.attachmentFileName);
        }
        [fileInStream open];
#define MAX_SEND_PACK_LEN       1024
        NSInteger remainSize = attachment.attachmentSize;
        while ([fileInStream hasBytesAvailable]) {
            NSInteger sendSize = remainSize > 1024 ? 1024 : remainSize;
            if (remainSize > 0) {
                // TODO maybe bind several packet and send
                NSOutputStream *outStreamAttach = [IVAN_NET_STREAM getWriteFlushOutStream];

                [StreamPeer writeInt8WithOutStream:outStreamAttach int8Value:MSG_TYPE_FILEATTCHSEG];
                [StreamPeer writeInt32WithOutStream:outStreamAttach intValue:[email getHashCode]];
                [StreamPeer writeInt32WithOutStream:outStreamAttach intValue:attachment.attachmentIndex];
                [StreamPeer writeInt32WithOutStream:outStreamAttach intValue:attachment.cbIndex];
                
                [StreamPeer writeInt32WithOutStream:outStreamAttach intValue:sendSize];
                
                UInt8 * sendBytes = (UInt8 *)malloc(sendSize);
                [StreamPeer readBytesWithInStream:fileInStream ptr:sendBytes maxLength:sendSize];
                
                [StreamPeer writeBytesWithOutStream:outStreamAttach ptr:sendBytes maxLength:sendSize];
                [IVAN_NET_STREAM writeFlush:outStreamAttach forMode:MODE_ASYNC];
                  
                free(sendBytes);
                sendBytes = NULL;
                remainSize -= sendSize;
                attachment.cbIndex += sendSize;
            } else {
                break;
            }
        }
        
        TTLOGF_INFO(@"Send attachment <hashcode:%u, size:%d>", [email getHashCode], attachment.attachmentSize);
        [fileInStream close];
        fileInStream = nil;
        assert(remainSize == 0);
    }
}

- (void)sendAttachment:(Attachment *)attachment
{
    // read the file

}

- (void)sendKeepAlive
{
    NSOutputStream *outStream = [IVAN_NET_STREAM getWriteFlushOutStream];
    
    [StreamPeer writeInt8WithOutStream:outStream int8Value:MSG_TYPE_KEEPALIVE];
    [IVAN_NET_STREAM writeFlush:outStream forMode:MODE_SYNC];
}
@end
